package com.haohan.cloud.scm.product.core.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.haohan.cloud.scm.api.constant.NumberPrefixConstant;
import com.haohan.cloud.scm.api.constant.ScmCommonConstant;
import com.haohan.cloud.scm.api.constant.enums.product.*;
import com.haohan.cloud.scm.api.constant.enums.purchase.BiddingStatusEnum;
import com.haohan.cloud.scm.api.goods.entity.GoodsModel;
import com.haohan.cloud.scm.api.goods.feign.GoodsModelFeignService;
import com.haohan.cloud.scm.api.product.dto.CreateLossRecordDTO;
import com.haohan.cloud.scm.api.product.dto.ProcessingResultDTO;
import com.haohan.cloud.scm.api.product.dto.ProcessingSourceDTO;
import com.haohan.cloud.scm.api.product.entity.ProductInfo;
import com.haohan.cloud.scm.api.product.entity.ProductLossRecord;
import com.haohan.cloud.scm.api.product.entity.SortingOrderDetail;
import com.haohan.cloud.scm.api.product.req.ProductMatchSortingReq;
import com.haohan.cloud.scm.api.product.req.QueryGoodsStorageReq;
import com.haohan.cloud.scm.api.product.req.UpdateInventoryReq;
import com.haohan.cloud.scm.api.product.trans.ProductInfoTrans;
import com.haohan.cloud.scm.api.purchase.entity.PurchaseOrderDetail;
import com.haohan.cloud.scm.api.purchase.feign.PurchaseOrderDetailFeignService;
import com.haohan.cloud.scm.api.purchase.req.PurchaseOrderDetailReq;
import com.haohan.cloud.scm.api.purchase.req.PurchaseQueryOrderDetailReq;
import com.haohan.cloud.scm.api.saleb.entity.BuyOrder;
import com.haohan.cloud.scm.api.saleb.entity.BuyOrderDetail;
import com.haohan.cloud.scm.api.wms.entity.ExitWarehouseDetail;
import com.haohan.cloud.scm.api.wms.trans.ExitWarehouseDetailTrans;
import com.haohan.cloud.scm.common.tools.exception.EmptyDataException;
import com.haohan.cloud.scm.common.tools.exception.ErrorDataException;
import com.haohan.cloud.scm.common.tools.util.RUtil;
import com.haohan.cloud.scm.common.tools.util.ScmIncrementUtil;
import com.haohan.cloud.scm.product.core.IProductInfoService;
import com.haohan.cloud.scm.product.service.ProductInfoService;
import com.haohan.cloud.scm.product.service.ProductLossRecordService;
import com.haohan.cloud.scm.product.service.SortingOrderDetailService;
import com.haohan.cloud.scm.product.utils.ScmProductUtils;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @author xwx
 * @date 2019/5/27
 */
@Service
@AllArgsConstructor
public class IProductInfoServiceImpl implements IProductInfoService {

    private final ProductInfoService productInfoService;
    private final PurchaseOrderDetailFeignService purchaseOrderDetailFeignService;
    private final GoodsModelFeignService goodsModelFeignService;
    private final ScmIncrementUtil scmIncrementUtil;
    private final ProductLossRecordService productLossRecordService;
    private final SortingOrderDetailService sortingOrderDetailService;
    private final ScmProductUtils scmProductUtils;

    /**
     * 根据采购部采购单明细  创建 货品信息
     *
     * @param req pmId   purchaseDetailSn采购单明细id
     * @return
     */
    @Override
    public ProductInfo addProductInfo(PurchaseQueryOrderDetailReq req) {
        ProductInfo info = new ProductInfo();
        info.setPmId(req.getPmId());
        info.setPurchaseDetailSn(req.getPurchaseDetailSn());
        ProductInfo one = productInfoService.getOne(Wrappers.query(info));
        if (one != null) {
            throw new ErrorDataException("货品信息已经存在");
        }
        //关联采购单明细： purchaseDetailSn采购单明细id  中标状态为已中标
        PurchaseOrderDetailReq orderDetailReq = new PurchaseOrderDetailReq();
        orderDetailReq.setPmId(req.getPmId());
        orderDetailReq.setPurchaseDetailSn(req.getPurchaseDetailSn());
        orderDetailReq.setBiddingStatus(BiddingStatusEnum.alreadyBidding);
        R r = purchaseOrderDetailFeignService.getOneByPurchaseOrderDetailReq(orderDetailReq, SecurityConstants.FROM_IN);
        if (!RUtil.isSuccess(r) || r.getData() == null) {
            throw new ErrorDataException("关联采购单明细出错");
        }
        PurchaseOrderDetail orderDetail = BeanUtil.toBean(r.getData(), PurchaseOrderDetail.class);

        ProductInfo productInfo = ProductInfoTrans.trans(orderDetail);
        // 货品有效期设置
        LocalDateTime validityTime = req.getValidityTime();
        if (null == validityTime) {
            validityTime = LocalDateTime.now().plusDays(ScmCommonConstant.VALIDITY_DAY);
        }
        productInfo.setValidityTime(validityTime);
        productInfoService.save(productInfo);
        return productInfo;
    }

    /**
     * 更新库存余量
     *
     * @param req
     * @return
     */
    @Override
    public Boolean updateInventory(UpdateInventoryReq req) {
        // 入参: goodsModelId
        // 查询 货品信息表 正常状态的货品,数量求和 为 库存数量
        QueryWrapper<ProductInfo> query = new QueryWrapper<>();
        BigDecimal sumStorage = BigDecimal.ZERO;
        query.lambda()
                .eq(ProductInfo::getPmId, req.getPmId())
                .eq(ProductInfo::getGoodsModelId, req.getGoodsModelId())
                .eq(ProductInfo::getProductStatus, ProductStatusEnum.normal)
                .eq(ProductInfo::getProductQuality, ProductQialityEnum.normal);
        List<ProductInfo> list = productInfoService.list(query);
        if (!list.isEmpty()) {
            throw new EmptyDataException("数据为空");
        }
        for (ProductInfo info : list) {
            sumStorage = sumStorage.add(info.getProductNumber());
        }
        // 更新 零售商品规格的 库存数量(加上虚拟库存数量)
        GoodsModel gmReq = new GoodsModel();
        gmReq.setId(req.getGoodsModelId());
        //查goods
        R rg = goodsModelFeignService.getById(req.getGoodsModelId(), SecurityConstants.FROM_IN);
        if (!RUtil.isSuccess(rg) || rg.getData() == null) {
            throw new EmptyDataException();
        }
        GoodsModel model = BeanUtil.toBean(rg.getData(), GoodsModel.class);
        if (model.getStocksForewarn() == null) {
            model.setStocksForewarn(ScmCommonConstant.INIT_VIRTUAL_STORAGE);
        }
        gmReq.setModelStorage(sumStorage.add(new BigDecimal(model.getStocksForewarn())));
        gmReq.setStocksForewarn(model.getStocksForewarn());
        R r = goodsModelFeignService.updateById(gmReq, SecurityConstants.FROM_IN);
        if (!RUtil.isSuccess(r)) {
            throw new ErrorDataException("更新库存余量失败");
        }
        // 返回:
        return true;
    }

    /**
     * 货品报损/去皮 (拆分为2个 状态为：一个正常 一个损耗)
     * 货品未保存
     *
     * @param sourceDTO 必须参数: sourceProductSn / subProductNum
     * @return ProcessingResultDTO
     * 返回结果: subProduct / lossProduct / sourceProduct
     */
    @Override
    public ProcessingResultDTO productPeeling(ProcessingSourceDTO sourceDTO) {
        ProductInfo source = productInfoService.fetchBySn(sourceDTO.getSourceProductSn());
        if (null == source) {
            throw new ErrorDataException("货品有误");
        }
        // 创建 2个新货品
        ProductInfo normal = initFromSource(source, sourceDTO.getSubProductNum());
        // 损耗货品设置
        BigDecimal lossNum = source.getProductNumber().subtract(sourceDTO.getSubProductNum());
        ProductInfo loss = initFromSource(source, lossNum);
        loss.setProductStatus(ProductStatusEnum.loss);
        // 原货品 状态修改
        source.setProductStatus(ProductStatusEnum.split);
        source.setProcessingType(ProcessingTypeEnum.peeling);
        // todo 货品 修改/保存
        ProcessingResultDTO resultDTO = new ProcessingResultDTO();
        resultDTO.setSubProduct(normal);
        resultDTO.setLossProduct(loss);
        resultDTO.setSourceProduct(source);
        return resultDTO;
    }

    /**
     * 根据原货品和新货品数量创建货品
     *
     * @param source 原货品完整信息
     * @param num    新货品数量
     * @return
     */
    private ProductInfo initFromSource(ProductInfo source, BigDecimal num) {
        ProductInfo normal = ProductInfoTrans.initFromSource(source, num);
        String normalSn = scmIncrementUtil.inrcSnByClass(ProductInfo.class, NumberPrefixConstant.PRODUCT_INFO_SN_PRE);
        normal.setProductSn(normalSn);
        return normal;
    }

    /**
     * 货品分装 (拆分为多个正常 可有一个耗损)
     * 货品未保存
     *
     * @param sourceDTO 必须参数: sourceProductSn / subProductNumList
     * @return ProcessingResultDTO
     * 返回结果: subProductList / sourceProduct / lossProduct(无损耗时为null)
     */
    @Override
    public ProcessingResultDTO productSplitting(ProcessingSourceDTO sourceDTO) {
        ProductInfo source = productInfoService.fetchBySn(sourceDTO.getSourceProductSn());
        // 创建 多个新货品
        List<ProductInfo> subProductList = new ArrayList<>();
        BigDecimal lossNum = source.getProductNumber();
        for (BigDecimal num : sourceDTO.getSubProductNumList()) {
            lossNum = lossNum.subtract(num);
            if (num.compareTo(BigDecimal.ZERO) <= 0 || lossNum.compareTo(BigDecimal.ZERO) < 0) {
                throw new ErrorDataException("货品数量有误");
            }
            ProductInfo normal = initFromSource(source, num);
            subProductList.add(normal);
        }
        ProcessingResultDTO resultDTO = new ProcessingResultDTO();
        resultDTO.setSubProductList(subProductList);
        // 存在损耗时
        if (lossNum.compareTo(BigDecimal.ZERO) > 0) {
            ProductInfo loss = initFromSource(source, lossNum);
            loss.setProductStatus(ProductStatusEnum.loss);
            if (!productInfoService.save(loss)) {
                throw new ErrorDataException();
            }
            resultDTO.setLossProduct(loss);
        }
        // 原货品 状态修改
        source.setProductStatus(ProductStatusEnum.split);
        source.setProcessingType(ProcessingTypeEnum.peeling);
        // todo 货品 修改/保存
        resultDTO.setSourceProduct(source);
        return resultDTO;
    }

    /**
     * TODO 货品组合 (按配方 合并为一个 原始货品)
     * 需生成货品加工记录
     *
     * @param sourceDTO
     * @return
     */
    @Override
    public ProcessingResultDTO productPacking(ProcessingSourceDTO sourceDTO) {
        // 创建 1个新货品
        // 创建加工记录
        return null;
    }

    /**
     * 生成货品损耗记录
     * ProductLossRecord 已保存
     *
     * @param createDTO 必须参数： source / normal / loss / relationSn / lossType
     * @return 损耗记录
     */
    @Override
    public ProductLossRecord createLossRecord(CreateLossRecordDTO createDTO) {
        // 创建损耗记录
        ProductLossRecord lossRecord = new ProductLossRecord();
        lossRecord.setProductSn(createDTO.getSource().getProductSn());
        lossRecord.setAfterProductSn(createDTO.getNormal().getProductSn());
        lossRecord.setLossProductSn(createDTO.getLoss().getProductSn());
        lossRecord.setLossType(createDTO.getLossType());
        lossRecord.setUnit(createDTO.getSource().getUnit());
        lossRecord.setOriginalNumber(createDTO.getSource().getProductNumber());
        lossRecord.setResultNumber(createDTO.getNormal().getProductNumber());
        lossRecord.setLossNumber(createDTO.getLoss().getProductNumber());
        BigDecimal lossRate = lossRecord.getLossNumber().divide(lossRecord.getOriginalNumber(), 2, BigDecimal.ROUND_HALF_UP);
        lossRecord.setLossRate(lossRate);
        lossRecord.setRecordTime(LocalDateTime.now());
        // 根据损耗类型 设置对应编号
        switch (createDTO.getLossType()) {
            case purchase:
                lossRecord.setPurchaseDetailSn(createDTO.getRelationSn());
                break;
            case handle:
                lossRecord.setProcessingSn(createDTO.getRelationSn());
                break;
            case select:
                lossRecord.setSortingDetailSn(createDTO.getRelationSn());
                break;
            case delivery:
                lossRecord.setShipDetailSn(createDTO.getRelationSn());
                break;
            case storage:
                lossRecord.setWarehouseInventorySn(createDTO.getRelationSn());
                break;
            case adjust:
                lossRecord.setAllotDetailSn(createDTO.getRelationSn());
                break;
            default:
                throw new ErrorDataException("报损类型有误");
        }
        if (!productLossRecordService.save(lossRecord)) {
            throw new ErrorDataException();
        }
        return lossRecord;
    }

    /**
     * 查询商品库存
     *
     * @param req
     * @return
     */
    @Override
    public ProductInfo queryGoodsStorage(QueryGoodsStorageReq req) {
        //查询货品信息
        ProductInfo queryInfo = new ProductInfo();
        queryInfo.setGoodsModelId(req.getGoodsModelId());
        queryInfo.setPmId(req.getPmId());
        queryInfo.setProductStatus(ProductStatusEnum.normal);
        queryInfo.setProductQuality(ProductQialityEnum.normal);
        // 在仓库的货品 计算入库存
        queryInfo.setProductPlaceStatus(ProductPlaceStatusEnum.stock);
        List<ProductInfo> list = productInfoService.list(Wrappers.query(queryInfo));
        // 查不到库存
        if (CollUtil.isEmpty(list)) {
            queryInfo.setProductNumber(BigDecimal.ZERO);
            return queryInfo;
        }
        BigDecimal sumStorage = BigDecimal.ZERO;
        for (ProductInfo info : list) {
            if(null==info.getProductNumber()){
                continue;
            }
            sumStorage = sumStorage.add(info.getProductNumber());
        }
        ProductInfo info = list.get(0);
        info.setProductNumber(sumStorage);

        //查询商品规格信息
        R goodsModelR = goodsModelFeignService.getById(req.getGoodsModelId(), SecurityConstants.FROM_IN);
        if (!RUtil.isSuccess(goodsModelR) || null == goodsModelR.getData()) {
            return info;
        }
        GoodsModel model = BeanUtil.toBean(goodsModelR.getData(), GoodsModel.class);
        //更新库存
        GoodsModel updModel = new GoodsModel();
        updModel.setId(model.getId());
        if (null == model.getStocksForewarn()) {
            updModel.setStocksForewarn(ScmCommonConstant.INIT_VIRTUAL_STORAGE);
            updModel.setModelStorage(sumStorage.add(new BigDecimal(ScmCommonConstant.INIT_VIRTUAL_STORAGE)));
        } else {
            updModel.setModelStorage(sumStorage.add(new BigDecimal(model.getStocksForewarn())));
        }
        // 库存变化时更新
        if (updModel.getModelStorage().compareTo(model.getModelStorage()) != 0) {
            goodsModelFeignService.updateById(updModel, SecurityConstants.FROM_IN);
        }
        return info;
    }

    /**
     * 根据 采购入库货品 匹配分拣
     *
     * @param req pmId / realBuyNum / purchaseDetailSn
     * @return 返回修改后的货品信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ProductInfo enterProductMatchSorting(ProductMatchSortingReq req) {
        BigDecimal realEnterNum = req.getRealBuyNum();
        String pmId = req.getPmId();
        // 查询货品
        QueryWrapper<ProductInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(ProductInfo::getPmId, pmId)
                .eq(ProductInfo::getProductPlaceStatus, ProductPlaceStatusEnum.purchase)
                .eq(ProductInfo::getPurchaseDetailSn, req.getPurchaseDetailSn());
        ProductInfo enterProduct = productInfoService.getOne(queryWrapper);
        if (null == enterProduct) {
            throw new ErrorDataException("入库货品信息有误");
        }
        // 匹配分拣
        realEnterNum = matchSorting(enterProduct, pmId, realEnterNum);

        // 修改货品数量(入库数量) 及 位置  有剩余数量时放入库存
        enterProduct.setProductNumber(realEnterNum);
        if (BigDecimal.ZERO.compareTo(realEnterNum) < 0) {
            enterProduct.setProductPlaceStatus(ProductPlaceStatusEnum.stock);
        } else {
            enterProduct.setProductPlaceStatus(ProductPlaceStatusEnum.product);
        }
        productInfoService.updateById(enterProduct);
        return enterProduct;
    }

    /**
     * 查询货品库存列表
     * @param page
     * @param info
     * @return
     */
    @Override
    public IPage queryStockNumList(Page page, ProductInfo info) {
        info.setProductPlaceStatus(ProductPlaceStatusEnum.stock);
        IPage<ProductInfo> p = productInfoService.page(page, Wrappers.query(info));
        if(p.getRecords().isEmpty()){
            throw new EmptyDataException("商品列表为空");
        }
        Map<String,ProductInfo> map = new LinkedHashMap<>();
        p.getRecords().forEach(product -> {
            if(ObjectUtil.isNull(map.get(product.getGoodsModelId()))){
                map.put(product.getGoodsModelId(),product);
            }else{
                map.get(product.getGoodsModelId())
                        .setProductNumber(map.get(product.getGoodsModelId()).getProductNumber().add(product.getProductNumber()));
            }
        });
        List<ProductInfo> list = new ArrayList<>();
        map.forEach((key,val) -> list.add(val));
        p.setRecords(list);
        return p;
    }

    /**
     * 匹配分拣
     *
     * @param enterProduct
     * @param pmId
     * @param realEnterNum
     * @return 返回剩余的实际入库数量
     */
    private BigDecimal matchSorting(ProductInfo enterProduct, String pmId, BigDecimal realEnterNum) {
        // 入库的商品规格id
        String goodsModelId = enterProduct.getGoodsModelId();
        // 1. 按 goodsModelId 查 待分拣 明细列表
        QueryWrapper<SortingOrderDetail> querySortingWrapper = new QueryWrapper<>();
        querySortingWrapper.lambda()
                .eq(SortingOrderDetail::getPmId, pmId)
                .eq(SortingOrderDetail::getSortingStatus, SortingStatusEnum.wait)
                .eq(SortingOrderDetail::getGoodsModelId, goodsModelId);
        List<SortingOrderDetail> sortingList = sortingOrderDetailService.list(querySortingWrapper.orderByDesc("create_date"));

        if (CollUtil.isNotEmpty(sortingList)) {
            // 未匹配 最小待分拣的 分拣单
            SortingOrderDetail minNeedSorting = null;
            // 2. 入库数量 对比 分拣明细数量
            for (SortingOrderDetail detail : sortingList) {
                BigDecimal purchaseNum = detail.getPurchaseNumber();
                // 2.1  满足时   创建出库单明细  修改分拣单明细状态  货品数量扣减
                if (realEnterNum.compareTo(purchaseNum) > -1) {
                    // 创建出库明细 修改分拣明细
                    createExitDetailAndSorting(enterProduct, detail, null);
                    // 修改 可入库数量
                    realEnterNum = realEnterNum.subtract(purchaseNum);
                } else {
                    // 2.2 不满足时 最小待分拣的 分拣单
                    if (null == minNeedSorting || minNeedSorting.getPurchaseNumber().compareTo(detail.getPurchaseNumber()) > 0) {
                        minNeedSorting = detail;
                    }
                }
            }
            // 3 利用库存匹配
            if (null != minNeedSorting) {
                // 3.1 查 库存 货品 列表
                QueryWrapper<ProductInfo> queryWrapper = new QueryWrapper<>();
                queryWrapper.lambda()
                        .eq(ProductInfo::getPmId, pmId)
                        .eq(ProductInfo::getProductPlaceStatus, ProductPlaceStatusEnum.stock)
                        .eq(ProductInfo::getGoodsModelId, goodsModelId);
                List<ProductInfo> stockProductList = productInfoService.list(queryWrapper.orderByDesc("product_number"));
                BigDecimal stockNum = realEnterNum;
                for (ProductInfo info : stockProductList) {
                    stockNum = stockNum.add(info.getProductNumber());
                }
                // 3.2 对比分拣数量
                if (minNeedSorting.getPurchaseNumber().compareTo(stockNum) < 1) {
                    // 需要使用的数量
                    BigDecimal needNum = minNeedSorting.getPurchaseNumber().subtract(realEnterNum);
                    realEnterNum = BigDecimal.ZERO;
                    List<String> productSnList = new ArrayList<>();
                    // 修改对应货品
                    ProductInfo updateProductInfo = new ProductInfo();
                    for (ProductInfo info : stockProductList) {
                        productSnList.add(info.getProductSn());
                        updateProductInfo.setId(info.getId());
                        needNum = needNum.subtract(info.getProductNumber());
                        if (BigDecimal.ZERO.compareTo(needNum) < 0) {
                            // 数量全锁定
                            updateProductInfo.setProductNumber(BigDecimal.ZERO);
                            updateProductInfo.setProductPlaceStatus(ProductPlaceStatusEnum.product);
                            productInfoService.updateById(updateProductInfo);
                        } else {
                            updateProductInfo.setProductNumber(needNum.abs());
                            updateProductInfo.setProductPlaceStatus(ProductPlaceStatusEnum.stock);
                            productInfoService.updateById(updateProductInfo);
                            break;
                        }
                    }
                    // 创建出库明细 修改分拣明细
                    createExitDetailAndSorting(enterProduct, minNeedSorting, CollUtil.join(productSnList, ","));
                }
            }
        }
        return realEnterNum;
    }

    /**
     * 创建出库明细 根据分拣单信息  (需buyOrderDetail 中采购数量和sortyingOrderDetail中数量一致)
     *
     * @param productInfo
     * @param detail
     * @param remarks     出库单 备注
     * @return
     */
    private void createExitDetailAndSorting(ProductInfo productInfo, SortingOrderDetail detail, String remarks) {
        // 创建出库明细 根据分拣单信息
        BuyOrderDetail buyOrderDetail = scmProductUtils.queryBuyOrderDetailBySn(detail.getBuyDetailSn());
        BuyOrder buyOrder = scmProductUtils.queryBuyOrderBySn(buyOrderDetail.getBuyId());
        ExitWarehouseDetail exitWarehouseDetail = ExitWarehouseDetailTrans.createDetailByBuyOrderDetail(productInfo, buyOrderDetail, buyOrder);
        // 数量以 分拣明细中的为准
        exitWarehouseDetail.setProductNumber(detail.getPurchaseNumber());
        exitWarehouseDetail.setRemarks(remarks);
        scmProductUtils.saveExitWarehouseDetail(exitWarehouseDetail);
        // 修改分拣明细
        SortingOrderDetail updateSortingDetail = new SortingOrderDetail();
        updateSortingDetail.setId(detail.getId());
        updateSortingDetail.setProductSn(productInfo.getProductSn());
        updateSortingDetail.setSortingStatus(SortingStatusEnum.sorting);
        sortingOrderDetailService.updateById(updateSortingDetail);
    }


}
